/*!
 * jQuery Mobile Touch Events @VERSION
 * http://jquerymobile.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

//>>label: Touch
//>>group: Events
//>>description: Touch events including: touchstart, touchmove, touchend, tap, taphold, swipe, swipeleft, swiperight
(function ($) {
	$.mobile = {};
}(jQuery));
(function ($, undefined) {
	var support = {
		touch: "ontouchend" in document
	};

	$.mobile.support = $.mobile.support || {};
	$.extend($.support, support);
	$.extend($.mobile.support, support);
}(jQuery));

/*!
 * jQuery Mobile Navigate Event @VERSION
 * http://jquerymobile.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

//>>label: Navigate
//>>group: Events
//>>description: Provides a wrapper around hashchange and popstate
//>>docs: http://api.jquerymobile.com/navigate/
//>>demos: http://api.jquerymobile.com/@VERSION/navigation/

// TODO break out pushstate support test so we don't depend on the whole thing
( function( factory ) {
	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define( [
			"jquery",
			"./../ns",
			"./../support" ], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
} )( function( $ ) {

var $win = $.mobile.window, self,
	dummyFnToInitNavigate = function() {};

$.event.special.beforenavigate = {
	setup: function() {
		$win.on( "navigate", dummyFnToInitNavigate );
	},

	teardown: function() {
		$win.off( "navigate", dummyFnToInitNavigate );
	}
};

$.event.special.navigate = self = {
	bound: false,

	pushStateEnabled: true,

	originalEventName: undefined,

	// If pushstate support is present and push state support is defined to
	// be true on the mobile namespace.
	isPushStateEnabled: function() {
		return $.support.pushState &&
			$.mobile.pushStateEnabled === true &&
			this.isHashChangeEnabled();
	},

	// !! assumes mobile namespace is present
	isHashChangeEnabled: function() {
		return $.mobile.hashListeningEnabled === true;
	},

	// TODO a lot of duplication between popstate and hashchange
	popstate: function( event ) {
		var newEvent, beforeNavigate, state;

		if ( event.isDefaultPrevented() ) {
			return;
		}

		newEvent = new $.Event( "navigate" );
		beforeNavigate = new $.Event( "beforenavigate" );
		state = event.originalEvent.state || {};

		beforeNavigate.originalEvent = event;
		$win.trigger( beforeNavigate );

		if ( beforeNavigate.isDefaultPrevented() ) {
			return;
		}

		if ( event.historyState ) {
			$.extend( state, event.historyState );
		}

		// Make sure the original event is tracked for the end
		// user to inspect incase they want to do something special
		newEvent.originalEvent = event;

		// NOTE we let the current stack unwind because any assignment to
		//      location.hash will stop the world and run this event handler. By
		//      doing this we create a similar behavior to hashchange on hash
		//      assignment
		setTimeout( function() {
			$win.trigger( newEvent, {
				state: state
			} );
		}, 0 );
	},

	hashchange: function( event /*, data */ ) {
		var newEvent = new $.Event( "navigate" ),
			beforeNavigate = new $.Event( "beforenavigate" );

		beforeNavigate.originalEvent = event;
		$win.trigger( beforeNavigate );

		if ( beforeNavigate.isDefaultPrevented() ) {
			return;
		}

		// Make sure the original event is tracked for the end
		// user to inspect incase they want to do something special
		newEvent.originalEvent = event;

		// Trigger the hashchange with state provided by the user
		// that altered the hash
		$win.trigger( newEvent, {
			// Users that want to fully normalize the two events
			// will need to do history management down the stack and
			// add the state to the event before this binding is fired
			// TODO consider allowing for the explicit addition of callbacks
			//      to be fired before this value is set to avoid event timing issues
			state: event.hashchangeState || {}
		} );
	},

	// TODO We really only want to set this up once
	//      but I'm not clear if there's a beter way to achieve
	//      this with the jQuery special event structure
	setup: function( /* data, namespaces */ ) {
		if ( self.bound ) {
			return;
		}

		self.bound = true;

		if ( self.isPushStateEnabled() ) {
			self.originalEventName = "popstate";
			$win.bind( "popstate.navigate", self.popstate );
		} else if ( self.isHashChangeEnabled() ) {
			self.originalEventName = "hashchange";
			$win.bind( "hashchange.navigate", self.hashchange );
		}
	}
};

return $.event.special.navigate;
} );

/*!
 * jQuery Mobile Orientation Change Event @VERSION
 * http://jquerymobile.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

//>>label: Orientation Change
//>>group: Events
//>>description: Provides a wrapper around the inconsistent browser implementations of orientationchange
//>>docs: http://api.jquerymobile.com/orientationchange/

( function( factory ) {
	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define( [
			"jquery",
			"../support/orientation",
			"./throttledresize" ], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
} )( function( $ ) {

var win = $( window ),
	event_name = "orientationchange",
	get_orientation,
	last_orientation,
	initial_orientation_is_landscape,
	initial_orientation_is_default,
	portrait_map = { "0": true, "180": true },
	ww, wh, landscape_threshold;

// It seems that some device/browser vendors use window.orientation values 0 and 180 to
// denote the "default" orientation. For iOS devices, and most other smart-phones tested,
// the default orientation is always "portrait", but in some Android and RIM based tablets,
// the default orientation is "landscape". The following code attempts to use the window
// dimensions to figure out what the current orientation is, and then makes adjustments
// to the to the portrait_map if necessary, so that we can properly decode the
// window.orientation value whenever get_orientation() is called.
//
// Note that we used to use a media query to figure out what the orientation the browser
// thinks it is in:
//
//     initial_orientation_is_landscape = $.mobile.media("all and (orientation: landscape)");
//
// but there was an iPhone/iPod Touch bug beginning with iOS 4.2, up through iOS 5.1,
// where the browser *ALWAYS* applied the landscape media query. This bug does not
// happen on iPad.

if ( $.support.orientation ) {

	// Check the window width and height to figure out what the current orientation
	// of the device is at this moment. Note that we've initialized the portrait map
	// values to 0 and 180, *AND* we purposely check for landscape so that if we guess
	// wrong, , we default to the assumption that portrait is the default orientation.
	// We use a threshold check below because on some platforms like iOS, the iPhone
	// form-factor can report a larger width than height if the user turns on the
	// developer console. The actual threshold value is somewhat arbitrary, we just
	// need to make sure it is large enough to exclude the developer console case.

	ww = window.innerWidth || win.width();
	wh = window.innerHeight || win.height();
	landscape_threshold = 50;

	initial_orientation_is_landscape = ww > wh && ( ww - wh ) > landscape_threshold;

	// Now check to see if the current window.orientation is 0 or 180.
	initial_orientation_is_default = portrait_map[ window.orientation ];

	// If the initial orientation is landscape, but window.orientation reports 0 or 180, *OR*
	// if the initial orientation is portrait, but window.orientation reports 90 or -90, we
	// need to flip our portrait_map values because landscape is the default orientation for
	// this device/browser.
	if ( ( initial_orientation_is_landscape && initial_orientation_is_default ) || ( !initial_orientation_is_landscape && !initial_orientation_is_default ) ) {
		portrait_map = { "-90": true, "90": true };
	}
}

// If the event is not supported natively, this handler will be bound to
// the window resize event to simulate the orientationchange event.
function handler() {
	// Get the current orientation.
	var orientation = get_orientation();

	if ( orientation !== last_orientation ) {
		// The orientation has changed, so trigger the orientationchange event.
		last_orientation = orientation;
		win.trigger( event_name );
	}
}

$.event.special.orientationchange = $.extend( {}, $.event.special.orientationchange, {
	setup: function() {
		// If the event is supported natively, return false so that jQuery
		// will bind to the event using DOM methods.
		if ( $.support.orientation && !$.event.special.orientationchange.disabled ) {
			return false;
		}

		// Get the current orientation to avoid initial double-triggering.
		last_orientation = get_orientation();

		// Because the orientationchange event doesn't exist, simulate the
		// event by testing window dimensions on resize.
		win.bind( "throttledresize", handler );
	},
	teardown: function() {
		// If the event is not supported natively, return false so that
		// jQuery will unbind the event using DOM methods.
		if ( $.support.orientation && !$.event.special.orientationchange.disabled ) {
			return false;
		}

		// Because the orientationchange event doesn't exist, unbind the
		// resize event handler.
		win.unbind( "throttledresize", handler );
	},
	add: function( handleObj ) {
		// Save a reference to the bound event handler.
		var old_handler = handleObj.handler;

		handleObj.handler = function( event ) {
			// Modify event object, adding the .orientation property.
			event.orientation = get_orientation();

			// Call the originally-bound event handler and return its result.
			return old_handler.apply( this, arguments );
		};
	}
} );

// Get the current page orientation. This method is exposed publicly, should it
// be needed, as jQuery.event.special.orientationchange.orientation()
$.event.special.orientationchange.orientation = get_orientation = function() {
	var isPortrait = true,
		elem = document.documentElement;

	// prefer window orientation to the calculation based on screensize as
	// the actual screen resize takes place before or after the orientation change event
	// has been fired depending on implementation (eg android 2.3 is before, iphone after).
	// More testing is required to determine if a more reliable method of determining the new screensize
	// is possible when orientationchange is fired. (eg, use media queries + element + opacity)
	if ( $.support.orientation ) {
		// if the window orientation registers as 0 or 180 degrees report
		// portrait, otherwise landscape
		isPortrait = portrait_map[ window.orientation ];
	} else {
		isPortrait = elem && elem.clientWidth / elem.clientHeight < 1.1;
	}

	return isPortrait ? "portrait" : "landscape";
};

$.fn[ event_name ] = function( fn ) {
	return fn ? this.bind( event_name, fn ) : this.trigger( event_name );
};

// jQuery < 1.8
if ( $.attrFn ) {
	$.attrFn[ event_name ] = true;
}

return $.event.special;
} );

/*!
 * jQuery Mobile Scroll Events @VERSION
 * http://jquerymobile.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

//>>label: Scroll
//>>group: Events
//>>description: Scroll events including: scrollstart, scrollstop

( function( factory ) {
	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define( [ "jquery" ], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
} )( function( $ ) {

var scrollEvent = "touchmove scroll";

// setup new event shortcuts
$.each( [ "scrollstart", "scrollstop" ], function( i, name ) {

	$.fn[ name ] = function( fn ) {
		return fn ? this.bind( name, fn ) : this.trigger( name );
	};

	// jQuery < 1.8
	if ( $.attrFn ) {
		$.attrFn[ name ] = true;
	}
} );

// also handles scrollstop
$.event.special.scrollstart = {

	enabled: true,
	setup: function() {

		var thisObject = this,
			$this = $( thisObject ),
			scrolling,
			timer;

		function trigger( event, state ) {
			var originalEventType = event.type;

			scrolling = state;

			event.type = scrolling ? "scrollstart" : "scrollstop";
			$.event.dispatch.call( thisObject, event );
			event.type = originalEventType;
		}

		var scrollStartHandler = $.event.special.scrollstart.handler = function ( event ) {

			if ( !$.event.special.scrollstart.enabled ) {
				return;
			}

			if ( !scrolling ) {
				trigger( event, true );
			}

			clearTimeout( timer );
			timer = setTimeout( function() {
				trigger( event, false );
			}, 50 );
		};

		// iPhone triggers scroll after a small delay; use touchmove instead
		$this.on( scrollEvent, scrollStartHandler );
	},
	teardown: function() {
		$( this ).off( scrollEvent, $.event.special.scrollstart.handler );
	}
};

$.each( {
	scrollstop: "scrollstart"
}, function( event, sourceEvent ) {

	$.event.special[ event ] = {
		setup: function() {
			$( this ).bind( sourceEvent, $.noop );
		},
		teardown: function() {
			$( this ).unbind( sourceEvent );
		}
	};
} );

return $.event.special;
} );

/*!
 * jQuery Mobile Throttled Resize @VERSION
 * http://jquerymobile.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

//>>label: Throttled Resize
//>>group: Events
//>>description: Fires a resize event with a slight delay to prevent excessive callback invocation
//>>docs: http://api.jquerymobile.com/throttledresize/

( function( factory ) {
	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define( [ "jquery" ], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
} )( function( $ ) {

var throttle = 250,
	lastCall = 0,
	heldCall,
	curr,
	diff,
	handler = function() {
		curr = ( new Date() ).getTime();
		diff = curr - lastCall;

		if ( diff >= throttle ) {

			lastCall = curr;
			$( this ).trigger( "throttledresize" );

		} else {

			if ( heldCall ) {
				clearTimeout( heldCall );
			}

			// Promise a held call will still execute
			heldCall = setTimeout( handler, throttle - diff );
		}
	};

// throttled resize event
$.event.special.throttledresize = {
	setup: function() {
		$( this ).bind( "resize", handler );
	},
	teardown: function() {
		$( this ).unbind( "resize", handler );
	}
};

return $.event.special.throttledresize;
} );

/*!
 * jQuery Mobile Touch Support Test @VERSION
 * http://jquerymobile.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

//>>label: Touch support test
//>>group: Core
//>>description: Touch feature test
(function ($) {
        $.mobile = {};
    }(jQuery));
( function( factory ) {
	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define( [
			"jquery",
			"../ns" ], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
} )( function( $ ) {

var support = {
	touch: "ontouchend" in document
};

$.mobile.support = $.mobile.support || {};
$.extend( $.support, support );
$.extend( $.mobile.support, support );

return $.support;
} );

/*!
 * jQuery Mobile Touch Events @VERSION
 * http://jquerymobile.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

//>>label: Touch
//>>group: Events
//>>description: Touch events including: touchstart, touchmove, touchend, tap, taphold, swipe, swipeleft, swiperight

( function( factory ) {
	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define( [
			"jquery",
			"../vmouse",
			"../support/touch" ], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
} )( function( $ ) {
var $document = $( document ),
	supportTouch = $.mobile.support.touch,
	touchStartEvent = supportTouch ? "touchstart" : "mousedown",
	touchStopEvent = supportTouch ? "touchend" : "mouseup",
	touchMoveEvent = supportTouch ? "touchmove" : "mousemove";

// setup new event shortcuts
$.each( ( "touchstart touchmove touchend " +
"tap taphold " +
"swipe swipeleft swiperight" ).split( " " ), function( i, name ) {

	$.fn[ name ] = function( fn ) {
		return fn ? this.bind( name, fn ) : this.trigger( name );
	};

	// jQuery < 1.8
	if ( $.attrFn ) {
		$.attrFn[ name ] = true;
	}
} );

function triggerCustomEvent( obj, eventType, event, bubble ) {
	var originalType = event.type;
	event.type = eventType;
	if ( bubble ) {
		$.event.trigger( event, undefined, obj );
	} else {
		$.event.dispatch.call( obj, event );
	}
	event.type = originalType;
}

// also handles taphold
$.event.special.tap = {
	tapholdThreshold: 750,
	emitTapOnTaphold: true,
	setup: function() {
		var thisObject = this,
			$this = $( thisObject ),
			isTaphold = false;

		$this.bind( "vmousedown", function( event ) {
			isTaphold = false;
			if ( event.which && event.which !== 1 ) {
				return true;
			}

			var origTarget = event.target,
				timer, clickHandler;

			function clearTapTimer() {
				if ( timer ) {
					$this.bind( "vclick", clickHandler );
					clearTimeout( timer );
				}
			}

			function clearTapHandlers() {
				clearTapTimer();

				$this.unbind( "vclick", clickHandler )
					.unbind( "vmouseup", clearTapTimer );
				$document.unbind( "vmousecancel", clearTapHandlers );
			}

			clickHandler = function( event ) {
				clearTapHandlers();

				// ONLY trigger a 'tap' event if the start target is
				// the same as the stop target.
				if ( !isTaphold && origTarget === event.target ) {
					triggerCustomEvent( thisObject, "tap", event );
				} else if ( isTaphold ) {
					event.preventDefault();
				}
			};

			$this.bind( "vmouseup", clearTapTimer );

			$document.bind( "vmousecancel", clearTapHandlers );

			timer = setTimeout( function() {
				if ( !$.event.special.tap.emitTapOnTaphold ) {
					isTaphold = true;
				}
				timer = 0;
				triggerCustomEvent( thisObject, "taphold", $.Event( "taphold", { target: origTarget } ) );
			}, $.event.special.tap.tapholdThreshold );
		} );
	},
	teardown: function() {
		$( this ).unbind( "vmousedown" ).unbind( "vclick" ).unbind( "vmouseup" );
		$document.unbind( "vmousecancel" );
	}
};

// Also handles swipeleft, swiperight
$.event.special.swipe = {

	// More than this horizontal displacement, and we will suppress scrolling.
	scrollSupressionThreshold: 30,

	// More time than this, and it isn't a swipe.
	durationThreshold: 1000,

	// Swipe horizontal displacement must be more than this.
	horizontalDistanceThreshold: window.devicePixelRatio >= 2 ? 15 : 30,

	// Swipe vertical displacement must be less than this.
	verticalDistanceThreshold: window.devicePixelRatio >= 2 ? 15 : 30,

	getLocation: function( event ) {
		var winPageX = window.pageXOffset,
			winPageY = window.pageYOffset,
			x = event.clientX,
			y = event.clientY;

		if ( event.pageY === 0 && Math.floor( y ) > Math.floor( event.pageY ) ||
				event.pageX === 0 && Math.floor( x ) > Math.floor( event.pageX ) ) {

			// iOS4 clientX/clientY have the value that should have been
			// in pageX/pageY. While pageX/page/ have the value 0
			x = x - winPageX;
			y = y - winPageY;
		} else if ( y < ( event.pageY - winPageY ) || x < ( event.pageX - winPageX ) ) {

			// Some Android browsers have totally bogus values for clientX/Y
			// when scrolling/zooming a page. Detectable since clientX/clientY
			// should never be smaller than pageX/pageY minus page scroll
			x = event.pageX - winPageX;
			y = event.pageY - winPageY;
		}

		return {
			x: x,
			y: y
		};
	},

	start: function( event ) {
		var data = event.originalEvent.touches ?
				event.originalEvent.touches[ 0 ] : event,
			location = $.event.special.swipe.getLocation( data );
		return {
			time: ( new Date() ).getTime(),
			coords: [ location.x, location.y ],
			origin: $( event.target )
		};
	},

	stop: function( event ) {
		var data = event.originalEvent.touches ?
				event.originalEvent.touches[ 0 ] : event,
			location = $.event.special.swipe.getLocation( data );
		return {
			time: ( new Date() ).getTime(),
			coords: [ location.x, location.y ]
		};
	},

	handleSwipe: function( start, stop, thisObject, origTarget ) {
		if ( stop.time - start.time < $.event.special.swipe.durationThreshold &&
				Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.horizontalDistanceThreshold &&
				Math.abs( start.coords[ 1 ] - stop.coords[ 1 ] ) < $.event.special.swipe.verticalDistanceThreshold ) {
			var direction = start.coords[ 0 ] > stop.coords[ 0 ] ? "swipeleft" : "swiperight";

			triggerCustomEvent( thisObject, "swipe", $.Event( "swipe", { target: origTarget, swipestart: start, swipestop: stop } ), true );
			triggerCustomEvent( thisObject, direction, $.Event( direction, { target: origTarget, swipestart: start, swipestop: stop } ), true );
			return true;
		}
		return false;

	},

	// This serves as a flag to ensure that at most one swipe event event is
	// in work at any given time
	eventInProgress: false,

	setup: function() {
		var events,
			thisObject = this,
			$this = $( thisObject ),
			context = {};

		// Retrieve the events data for this element and add the swipe context
		events = $.data( this, "mobile-events" );
		if ( !events ) {
			events = { length: 0 };
			$.data( this, "mobile-events", events );
		}
		events.length++;
		events.swipe = context;

		context.start = function( event ) {

			// Bail if we're already working on a swipe event
			if ( $.event.special.swipe.eventInProgress ) {
				return;
			}
			$.event.special.swipe.eventInProgress = true;

			var stop,
				start = $.event.special.swipe.start( event ),
				origTarget = event.target,
				emitted = false;

			context.move = function( event ) {
				if ( !start || event.isDefaultPrevented() ) {
					return;
				}

				stop = $.event.special.swipe.stop( event );
				if ( !emitted ) {
					emitted = $.event.special.swipe.handleSwipe( start, stop, thisObject, origTarget );
					if ( emitted ) {

						// Reset the context to make way for the next swipe event
						$.event.special.swipe.eventInProgress = false;
					}
				}
				// prevent scrolling
				if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.scrollSupressionThreshold ) {
					event.preventDefault();
				}
			};

			context.stop = function() {
				emitted = true;

				// Reset the context to make way for the next swipe event
				$.event.special.swipe.eventInProgress = false;
				$document.off( touchMoveEvent, context.move );
				context.move = null;
			};

			$document.on( touchMoveEvent, context.move )
				.one( touchStopEvent, context.stop );
		};
		$this.on( touchStartEvent, context.start );
	},

	teardown: function() {
		var events, context;

		events = $.data( this, "mobile-events" );
		if ( events ) {
			context = events.swipe;
			delete events.swipe;
			events.length--;
			if ( events.length === 0 ) {
				$.removeData( this, "mobile-events" );
			}
		}

		if ( context ) {
			if ( context.start ) {
				$( this ).off( touchStartEvent, context.start );
			}
			if ( context.move ) {
				$document.off( touchMoveEvent, context.move );
			}
			if ( context.stop ) {
				$document.off( touchStopEvent, context.stop );
			}
		}
	}
};
$.each( {
	taphold: "tap",
	swipeleft: "swipe.left",
	swiperight: "swipe.right"
}, function( event, sourceEvent ) {

	$.event.special[ event ] = {
		setup: function() {
			$( this ).bind( sourceEvent, $.noop );
		},
		teardown: function() {
			$( this ).unbind( sourceEvent );
		}
	};
} );

return $.event.special;
} );

